/*
 * Copyright 2017 Matthew Tamlin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.matthewtamlin.fortytwo.library.answer_view;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.utils.Color;

import com.matthewtamlin.fortytwo.library.answer_view.DecoratedAnswerCard.Decorator;
import com.matthewtamlin.fortytwo.library.util.Utils;

import static com.matthewtamlin.java_utilities.checkers.NullChecker.checkNotNull;

/**
 * A {@link Decorator} which changes the colors of the target answer card, specifically the
 * background color and the text colors. The background colors are defined by supplying a {@link
 * ColorSupplier} to the constructor, and the text colors are automatically chosen to maximise
 * readability against the background. If animations are enabled, the colors are transitioned
 * smoothly.
 */
public class ColorFadeDecorator extends DecoratorAdapter {
    /**
     * Supplies the colors to use for the card background.
     */
    private final ColorSupplier colorSupplier;

    /**
     * Indicates whether or not a decoration is currently being applied to a view. This could be an
     * instantaneous update on the UI thread, or an asynchronous update using animators.
     */
    private boolean updateInProgress = false;

    /**
     * Indicates whether or not an update needs to be performed when possible.
     */
    private boolean updatePending = false;

    /**
     * Indicates whether or not the next update should be animated or instantaneous.
     */
    private boolean animateNextUpdate = false;

    /**
     * Constructs a new ColorFadeDecorator.
     *
     * @param colorSupplier supplies the card background colors, not null
     * @throws IllegalArgumentException if {@code colorSupplier} is null
     */
    public ColorFadeDecorator(final ColorSupplier colorSupplier) {
        this.colorSupplier = checkNotNull(colorSupplier, "colorSupplier cannot be null.");
    }

    /**
     * getColorSupplier
     *
     * @return the current color supplier
     */
    public ColorSupplier getColorSupplier() {
        return colorSupplier;
    }

    @Override
    public void decorate(final DecoratedAnswerCard cardToDecorate, final boolean animate) {
        checkNotNull(cardToDecorate, "cardToDecorate cannot be null.");

        updatePending = true;
        animateNextUpdate = animate;

        if (!updateInProgress) {
            updateBackgroundColor(cardToDecorate);
        }
    }

    /**
     * Applies the decoration to the supplied card, using animations if necessary. If this method is
     * called again while animations from a previous invocation are still running, the current
     * animations will complete before new ones are started.
     *
     * @param cardToDecorate the card to apply the decoration to, not null
     */
    private void updateBackgroundColor(final DecoratedAnswerCard cardToDecorate) {
        updateInProgress = true;
        updatePending = false;

        ShapeElement backElement = (ShapeElement) cardToDecorate.getCard().getBackgroundElement().getCurrentElement();
        int shapeColor = backElement.getRgbColors()[0].asArgbInt();
        final int startBackground = shapeColor == 0 ? -1 : shapeColor;
        final int targetBackground = colorSupplier.getColor(cardToDecorate.isMarked(),
            cardToDecorate.isSelected(), cardToDecorate.answerIsCorrect());

        final int startTextColor = cardToDecorate.getAnswerContainer().getTextColor().getValue();
        final int targetTextColor = Utils.calculateBestTextColor(targetBackground);

        if (!animateNextUpdate || getAnimationDurationMs() == 0) {
            backElement.setRgbColor(RgbColor.fromArgbInt(targetBackground));
            cardToDecorate.getCard().setBackground(backElement);

            Color textColor = new Color(targetTextColor);
            cardToDecorate.getAnswerContainer().setTextColor(textColor);
            cardToDecorate.getIdentifierContainer().setTextColor(textColor);
            updateInProgress = false;
        } else {
            AnimatorValue animatorValue = new AnimatorValue();
            animatorValue.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float v) {
                    // Set background color by proportionally blending start and target colors
                    final int backgroundColor = Utils.blendColors(startBackground,
                        targetBackground, v);
                    ShapeElement shapeElement = backElement;
                    shapeElement.setRgbColor(RgbColor.fromArgbInt(backgroundColor));
                    cardToDecorate.getCard().setBackground(shapeElement);

                    // Set text color by proportionally blending start and target colors
                    final int color = Utils.blendColors(startTextColor,
                        targetTextColor, v);
                    Color textColor = new Color(color);
                    cardToDecorate.getAnswerContainer().setTextColor(textColor);
                    cardToDecorate.getIdentifierContainer().setTextColor(textColor);
                }
            });
            animatorValue.setStateChangedListener(new Animator.StateChangedListener() {
                @Override
                public void onStart(Animator animator) {
                }

                @Override
                public void onStop(Animator animator) {
                }

                @Override
                public void onCancel(Animator animator) {
                }

                @Override
                public void onEnd(Animator animator) {
                    updateInProgress = false;

                    // If a new update was requested while the animation progressed, do it now
                    if (updatePending) {
                        updateBackgroundColor(cardToDecorate);
                    }
                }

                @Override
                public void onPause(Animator animator) {
                }

                @Override
                public void onResume(Animator animator) {
                }
            });
            animatorValue.setDuration(getAnimationDurationMs());
            animatorValue.start();
        }
    }

    /**
     * Supplies the colors to use in a ColorFadeDecorator.
     */
    public interface ColorSupplier {
        /**
         * Supplies the target background color for a card with the given properties.
         *
         * @param marked whether or not the card is currently marked
         * @param selected whether or not the card is currently selected
         * @param answerIsCorrect whether or not the answer displayed in the card is correct
         * @return the color to use for the card background, as an ARGB hex code
         */
        public int getColor(boolean marked, boolean selected, boolean answerIsCorrect);
    }
}